package gecko.lang;


import gecko.x.CastX;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author 陈永佳 (yoojiachen@gmail.com)
 */
public class TypedMap {

    private final Map<String, Object> mData;

    public TypedMap() {
        this(Collections.emptyMap());
    }

    public TypedMap(Map<String, Object> m) {
        this.mData = m;
    }

    public TypedMap newWith(TypedMap map) {
        Map<String, Object> newMap = new HashMap<>();
        newMap.putAll(this.mData);
        newMap.putAll(map.mData);
        return new TypedMap(newMap);
    }

    public boolean containsKey(String key) {
        return this.mData.containsKey(key);
    }

    public boolean isEmpty() {
        return this.mData.isEmpty();
    }

    public boolean isNotEmpty() {
        return !isEmpty();
    }

    //

    public String getString(String key) {
        return getString(key, "");
    }

    public String getString(String key, String def) {
        return CastX.toString(mData.getOrDefault(key, def));
    }

    public List<String> getStringList(String key) {
        return CastX.getList(mData.getOrDefault(key, Collections.emptyList()), CastX::toString);
    }

    public int getInt(String key) {
        return getInt(key, 0);
    }

    public int getInt(String key, int def) {
        return CastX.toInt(mData.getOrDefault(key, def));
    }

    public List<Integer> getIntList(String key) {
        return CastX.getList(mData.getOrDefault(key, Collections.emptyList()), CastX::toInt);
    }

    public long getLong(String key) {
        return getLong(key, 0L);
    }

    public long getLong(String key, long def) {
        return CastX.toLong(mData.getOrDefault(key, def));
    }

    public List<Long> getLongList(String key) {
        return CastX.getList(mData.getOrDefault(key, Collections.emptyList()), CastX::toLong);
    }

    public double getDouble(String key) {
        return getDouble(key, 0.0);
    }

    public double getDouble(String key, double def) {
        return CastX.toDouble(mData.getOrDefault(key, def));
    }

    public List<Double> getDoubleList(String key) {
        return CastX.getList(mData.getOrDefault(key, Collections.emptyList()), CastX::toDouble);
    }

    public boolean getBoolean(String key) {
        return getBoolean(key, false);
    }

    public boolean getBoolean(String key, boolean def) {
        return CastX.toBoolean(mData.getOrDefault(key, def));
    }

    public TypedMap getDictMap(String key) {
        return new TypedMap(CastX.toStrMap(mData.getOrDefault(key, Collections.emptyMap())));
    }

    public List<TypedMap> getDictMapList(String key) {
        return CastX.getList(mData.getOrDefault(key, Collections.emptyList()), m ->
                new TypedMap(CastX.toStrMap(m)));
    }

    public Map<String, Object> getStrMap(String key) {
        return CastX.toStrMap(mData.getOrDefault(key, Collections.emptyMap()));
    }

    public boolean fieldEqualTo(String key, String expected) {
        return expected.equals(getString(key));
    }

    public boolean fieldEqualTo(String key, int expected) {
        return expected == getInt(key);
    }

    public boolean fieldEqualTo(String key, long expected) {
        return expected == getLong(key);
    }

    public void forEach(BiConsumer<String, Object> consumer) {
        this.mData.forEach(consumer);
    }

    public Stream<Map.Entry<String, Object>> stream() {
        return this.mData.entrySet().stream();
    }

    public <V> Map<String, V> mapValues(Function<Object, V> function) {
        return stream().collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> function.apply(e.getValue())
        ));
    }

    public static TypedMap wrap(Map<String, Object> m) {
        return new TypedMap(m);
    }

    public static TypedMap empty() {
        return wrap(Collections.emptyMap());
    }

}
